package com.star.learn.excel.util;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.star.learn.excel.domain.Student;
import com.star.learn.excel.service.RabbitMQProducerService;
import com.star.learn.excel.service.RedisMetadataService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@Slf4j
public class ExcelUtil extends AnalysisEventListener<Student> {
    private static final int BATCH_COUNT = 1000;
    private final List<Student> dataList = new ArrayList<>();
    private final String sheetName;
    private final String filePath; // 用于日志或备份
    private final RedisMetadataService redisMetadataService;
    private final RabbitMQProducerService rabbitMQProducerService; // 发送触发消息
    private final String fileId;
    private int validCount = 0;

    // 构造函数，接收所需服务
    public ExcelUtil(String sheetName, String filePath, RedisMetadataService redisMetadataService,
                     RabbitMQProducerService rabbitMQProducerService, String fileId) {
        this.sheetName = sheetName;
        this.filePath = filePath;
        this.redisMetadataService = redisMetadataService;
        this.rabbitMQProducerService = rabbitMQProducerService;
        this.fileId = fileId;
    }

    @Override
    public void invoke(Student student, AnalysisContext context) {
        if (isValidStudent(student)) {
            validCount++;
            dataList.add(student);
            if (dataList.size() >= BATCH_COUNT) {
                processBatch(); // 处理批次：存Redis并发送触发MQ
                dataList.clear();
            }
        }
    }

    private boolean isValidStudent(Student student) {
        return StringUtils.hasText(student.getId()) &&
                student.getId().matches("^\\d{10,15}$") &&
                StringUtils.hasText(student.getName()) &&
                !containsSpecialCharacters(student.getName()) &&
                StringUtils.hasText(student.getClassName());
    }

    private boolean containsSpecialCharacters(String input) {
        return input.matches(".*[\\p{P}\\p{S}].*");
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        if (!dataList.isEmpty()) {
            processBatch(); // 处理最后一批数据
        }
        log.info("文件 {} (FileId: {}), Sheet {}: 所有数据解析完成，有效数据共{}条",
                new File(filePath).getName(), fileId, sheetName, validCount);
    }

    // 新的批次处理逻辑：存入Redis并发送触发消息
    private void processBatch() {
        if (dataList.isEmpty()) {
            return; // 没有数据需要处理
        }
        // 使用 RedisMetadataService 获取批次号
        int batchNum = redisMetadataService.getNextBatchNumber(fileId, sheetName);
        int batchSize = dataList.size();

        try {
            // 1. 将数据存储到 Redis
            // 如果 Redis 存储失败，会抛出 RuntimeException，进入 catch 块进行本地备份
            redisMetadataService.saveBatchData(fileId, sheetName, batchNum, dataList);
            log.debug("文件 {} (FileId: {}), Sheet {}, Batch {}: 批次数据已存入 Redis.",
                    new File(filePath).getName(), fileId, sheetName, batchNum);

            // 2. 更新元数据状态为 PENDING（表示数据已在 Redis，等待 MQ 触发消费）
            // 如果 Redis 元数据更新失败，也会抛出 RuntimeException，进入 catch 块进行本地备份
            redisMetadataService.updateBatchMetadata(fileId, sheetName, batchNum, batchSize, "PENDING");
            log.debug("文件 {} (FileId: {}), Sheet {}, Batch {}: Redis 元数据状态更新为 PENDING",
                    new File(filePath).getName(), fileId, sheetName, batchNum);

            // 3. 发送触发消息到 RabbitMQ (消息体只包含批次标识信息)
            // ProducerService 现在发送的是 trigger 消息
            // 这里的失败不会导致本地备份，因为数据已经在 Redis 中
            boolean sent = rabbitMQProducerService.sendAndConfirm(fileId, sheetName, batchNum, batchSize);

            if (sent) {
                log.debug("文件 {} (FileId: {}), Sheet {}, Batch {}: 触发消息发送到 MQ 成功.",
                        new File(filePath).getName(), fileId, sheetName, batchNum);
            } else {
                // MQ 触发消息发送或确认失败
                // 数据已在 Redis 中，状态是 PENDING。
                // BackupRecoveryTask 或监控可以发现长时间 PENDING 的批次，并重新发送触发消息。
                log.error("文件 {} (FileId: {}), Sheet {}, Batch {}: 触发消息发送到 MQ 失败或未确认. 数据已在 Redis 中.",
                        new File(filePath).getName(), fileId, sheetName, batchNum);
                throw new Exception("MQ发送或确认失败");
            }
        } catch (Exception e) {
            // 捕获 Redis 存储失败、元数据更新失败 或 初始 MQ 发送尝试失败（如果 sendAndConfirm 抛异常）
            log.warn("文件 {} (FileId: {}), Sheet {}, Batch {}: 尝试将失败的批次备份到本地.",
                    new File(filePath).getName(), fileId, sheetName, batchNum);
            backupToLocalFile(dataList, sheetName, fileId, batchNum);
        }
    }
    // 写失败时记录本地备份文件（兜底）
    private void backupToLocalFile(List<Student> data, String sheet, String fileId, int batchNum) {
        try {
            String path = String.format("backup/%s_%s_%d_%d.json", fileId, sheet, batchNum, System.currentTimeMillis());
            File file = new File(path);
            file.getParentFile().mkdirs();
            ObjectMapper mapper = new ObjectMapper();
            mapper.writeValue(file, data);
            log.info("已备份失败数据到本地: {}", path);
        } catch (IOException e) {
            log.error("备份失败数据到本地文件出错", e);
        }
    }
}